/**
 * @file ctrlzee_control.c (router)
 * @brief A Z-Stack module that provides an implementation of a control 
 *        endpoint on a router node. This endpoint functions as a receiver of
 *        control messages from the gateway.
 * @author Vamshi Ponnapalli
 */
/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "ZDObject.h"
#include "ctrlZee.h"
#include "hal_lcd.h"

/*******************************************************************************
 * Variable declarations
 ******************************************************************************/
// ----- Constants (macros) -----
#define CTRLZEE_CONTROL_OUT_CLUSTER_COUNT   0
#define CTRLZEE_CONTROL_IN_CLUSTER_COUNT    1

#define CTRLZEE_CONTROL_MAX_DEST_ADDR       2

// ----- Constants (variables) -----
//Output clusters.
const cId_t ctrlZee_controlOutClusters[];

//Input clusters.
const cId_t ctrlZee_controlInClusters[CTRLZEE_CONTROL_IN_CLUSTER_COUNT] =
{
  CTRLZEE_CLUSTER_ID_COMMAND
};

//Simple descriptor
const SimpleDescriptionFormat_t ctrlZee_controlSimpleDesc =
{
  CTRLZEE_ENDPOINT_CONTROL,
  CTRLZEE_PROFILE_ID,
  CTRLZEE_DEVICE_ID_LIGHT,
//  CTRLZEE_DEVICE_ID_HVAC,
  
  CTRLZEE_DEVICE_VERSION,
  CTRLZEE_FLAGS_DEFAULT,
  
  //Input clusters
  CTRLZEE_CONTROL_IN_CLUSTER_COUNT,
  (cId_t *)ctrlZee_controlInClusters,

  //Output clusters
  CTRLZEE_CONTROL_OUT_CLUSTER_COUNT,
  (cId_t *)ctrlZee_controlOutClusters
};

// ----- Global variables -----

//Endpoint descriptor
endPointDesc_t ctrlZee_controlEndpointDesc;

//Test message being sent to devices
byte ctrlZee_controlPingCounter=0;

//Task id of the ctrlzee module.
byte ctrlZee_controlTaskId;   

//Holds the current network state of the ctrlzee module.
devStates_t ctrlZee_controlNetworkState;

//Ctrl-Zee specific message counter.
byte ctrlZee_controlMessageId;

//Variable to hold the destination address for Zigbee communication
afAddrType_t ctrlZee_controlDestAddress[CTRLZEE_CONTROL_MAX_DEST_ADDR];

//Ctrl-Zee light status
byte ctrlZee_controlLightStatus;
byte ctrlZee_controlEnableReporting;

/*******************************************************************************
 * Static function declarations (private to this file)
 ******************************************************************************/
static void ctrlZee_controlProcessZDOMessage(zdoIncomingMsg_t *inMsg);
static void ctrlZee_controlProcessIncomingMessage(afIncomingMSGPacket_t *packet);
static void ctrlZee_controlReportStatus(void);
static void ctrlZee_controlSendMessage(afAddrType_t* destAddress, 
                                       ctrlzee_peer_cmd_t *message, 
                                       byte size);
static void ctrlZee_controlSendZDOMatchRequest(void);
/*******************************************************************************
 * Function implementations
 ******************************************************************************/
void ctrlZee_controlInit(byte taskId)
{
  //Basic initialization
  ctrlZee_controlTaskId = taskId;
  ctrlZee_controlNetworkState = DEV_INIT;
  ctrlZee_controlMessageId = 0;
    
  ctrlZee_controlLightStatus = 0;
  ctrlZee_controlEnableReporting = 0;

  //Initialize destination address(es)
  for(uint8 index=0; index < CTRLZEE_CONTROL_MAX_DEST_ADDR; index++)
  {
    ctrlZee_controlDestAddress[index].addrMode = (afAddrMode_t)AddrNotPresent;
    ctrlZee_controlDestAddress[index].endPoint = 0;
    ctrlZee_controlDestAddress[index].addr.shortAddr = 0;
  }
  // Fill out the endpoint description.
  ctrlZee_controlEndpointDesc.endPoint = CTRLZEE_ENDPOINT_CONTROL;
  ctrlZee_controlEndpointDesc.task_id = &ctrlZee_controlTaskId;
  ctrlZee_controlEndpointDesc.simpleDesc
            = (SimpleDescriptionFormat_t *)&ctrlZee_controlSimpleDesc;
  ctrlZee_controlEndpointDesc.latencyReq = noLatencyReqs;

  // Register the endpoint description with the AF
  afRegister(&ctrlZee_controlEndpointDesc);

  //Register for ZDO messages.
  ZDO_RegisterForZDOMsg(ctrlZee_controlTaskId, Match_Desc_rsp);
}
/******************************************************************************/
UINT16 ctrlZee_controlProcessEvent(byte taskId, UINT16 events)
{
  afIncomingMSGPacket_t *packet;
  (void)taskId;  // Intentionally unreferenced parameter

  //Check if the a system event was raised.
  if (events & SYS_EVENT_MSG)
  {
    //Read the incoming message(s).
    packet = (afIncomingMSGPacket_t *)osal_msg_receive(ctrlZee_controlTaskId);

    //Loop through all incoming messages.
    while (packet)
    {
      switch (packet->hdr.event)
      {
          //Callback message from the ZDO layer.
        case ZDO_CB_MSG:
          ctrlZee_controlProcessZDOMessage((zdoIncomingMsg_t *)packet);
          break;

          //Confirmation of a message packet sent.
        case AF_DATA_CONFIRM_CMD:
          // Action taken when confirmation is received.
          if (((afDataConfirm_t*)packet)->hdr.status != ZSuccess)
          {
            // The data wasn't delivered -- Do something
          }
          break;

          //Incoming message.
        case AF_INCOMING_MSG_CMD:
           ctrlZee_controlProcessIncomingMessage(packet);
          break;

          //Change of network state of the device.
        case ZDO_STATE_CHANGE:
          ctrlZee_controlNetworkState = (devStates_t)(packet->hdr.status);          
          
          if ((ctrlZee_controlNetworkState == DEV_ZB_COORD)
              || (ctrlZee_controlNetworkState == DEV_ROUTER)
              || (ctrlZee_controlNetworkState == DEV_END_DEVICE))
          {
            // Start sending "the" message in a regular interval.
            osal_start_timerEx(ctrlZee_controlTaskId,
                               CTRLZEE_OSAL_EVENT_REPORT_STATUS,
                               CTRLZEE_OSAL_REPORT_STATUS_DURATION);
          }
          break;

        default:
          break;
      }

      //Release the memory.
      osal_msg_deallocate((uint8 *)packet);

      //Read next message in the chain.
      packet = (afIncomingMSGPacket_t *)osal_msg_receive(ctrlZee_controlTaskId);
    }
    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }
  
  
  /**
  * "Report status" event received.
  */
  if (events & CTRLZEE_OSAL_EVENT_REPORT_STATUS)
  {
    if(ctrlZee_controlEnableReporting != 0)
    {
      //Send a ping message.
      ctrlZee_controlReportStatus();
      
      //Setup to send a ping again after a delay.
      osal_start_timerEx(ctrlZee_controlTaskId,
                         CTRLZEE_OSAL_EVENT_REPORT_STATUS,
                         CTRLZEE_OSAL_REPORT_STATUS_DURATION);
    }
    
    // return unprocessed events
    return (events ^ CTRLZEE_OSAL_EVENT_REPORT_STATUS);
  }
     
  // Discard unknown events
  return 0;
}
/******************************************************************************/
static void ctrlZee_controlProcessZDOMessage(zdoIncomingMsg_t *inMsg)
{
  switch (inMsg->clusterID)
  {
    case Match_Desc_rsp:
      {
        ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp(inMsg);
        if (pRsp)
        {
          //First find an open slot to store this address.
          afAddrType_t *destAddress = 0x00;          
          for(uint8 index=0; index < CTRLZEE_CONTROL_MAX_DEST_ADDR; index++)
          {
            if(ctrlZee_controlDestAddress[index].addrMode == afAddrNotPresent)
            {
              destAddress = &ctrlZee_controlDestAddress[index];
            }
            else if(ctrlZee_controlDestAddress[index].addr.shortAddr == 
                                                                  pRsp->nwkAddr)
            {
              destAddress = &ctrlZee_controlDestAddress[index];
              break;
            }
          }
          
          //Check if an open slot was found.
          if(destAddress != 0x00)
          {
            if (pRsp->status == ZSuccess && pRsp->cnt)
            {
              destAddress->addrMode = (afAddrMode_t)Addr16Bit;
              destAddress->addr.shortAddr = pRsp->nwkAddr;
              
              // Keep it simple, and pick up the first endpoint
              destAddress->endPoint = pRsp->epList[0];
            }
          }
          osal_mem_free(pRsp);
        }
      }
      break;
  }
}
/******************************************************************************/
static void ctrlZee_controlProcessIncomingMessage(afIncomingMSGPacket_t *pkt)
{
  ctrlzee_peer_cmd_t* message;
    
  switch (pkt->clusterId)
  {
  case CTRLZEE_CLUSTER_ID_COMMAND:
    message = (ctrlzee_peer_cmd_t*)pkt->cmd.Data;
    switch(message->command)
    {
    case CTRLZEE_PEER_CMD_PING:
#if defined(LCD_SUPPORTED)
      {
        char pingMessageText[] = "Ping: 000";
        byte index = 6;
        uint8 counter = 0;
        
        counter = message->arg1;
        pingMessageText[index++] += (counter/100);
        counter %= 100;
        pingMessageText[index++] += (counter/10);
        counter %= 10;
        pingMessageText[index++] += counter;        
        HalLcdWriteScreen(pingMessageText, "[CMD]");
      }
#endif    
    break;
    case CTRLZEE_PEER_CMD_ONOFF:   
      {
        if(message->arg1 == CTRLZEE_PEER_CMD_ARG_ON) 
        {
          ctrlZee_controlLightStatus = 0xFF;
        }
        else if (message->arg1 == CTRLZEE_PEER_CMD_ARG_OFF)
        {
          ctrlZee_controlLightStatus = 0x00;
        }
        else
        {
#if defined(LCD_SUPPORTED)
          char errMsg[] = "OnOff Command: 0";
          errMsg[15] = message->arg1;
          HalLcdWriteScreen(errMsg, "ERROR");
#endif
        }
#if defined(LCD_SUPPORTED)
        if(ctrlZee_controlLightStatus)
        {
          HalLcdWriteScreen("OnOff Command", "ON");
        }
        else
        {
          HalLcdWriteScreen("OnOff Command", "OFF");
        }
#endif
      }
    break;
    case CTRLZEE_PEER_CMD_REPORT_ONOFF:
      {
        if(message->arg1 == CTRLZEE_PEER_CMD_ARG_ON)
        {
          //Enable reporting
          ctrlZee_controlEnableReporting = 0xFF;
          
          //Set an event to renable periodic reporting
          osal_set_event(ctrlZee_controlTaskId, 
                         CTRLZEE_OSAL_EVENT_REPORT_STATUS);
        }
        else if(message->arg1 == CTRLZEE_PEER_CMD_ARG_OFF)
        {
          //Disable reporting
          ctrlZee_controlEnableReporting = 0x00;
        }
        else
        {
#if defined(LCD_SUPPORTED)
          char errMsg[] = "OnOff Command: 0";
          errMsg[15] = message->arg1;
          HalLcdWriteScreen(errMsg, "ERROR");
#endif
        }
#if defined(LCD_SUPPORTED)
        if(ctrlZee_controlEnableReporting)
        {
          HalLcdWriteScreen("Status Reporting", "ON");
        }
        else
        {
          HalLcdWriteScreen("Status Reporting", "OFF");
        }
#endif
      }
      break;
    case CTRLZEE_PEER_CMD_DISCOVER:
      {
        //Initiate a match request.
        ctrlZee_controlSendZDOMatchRequest();
      }
      break;
    }
  }
}
/******************************************************************************/
static void ctrlZee_controlReportStatus(void)
{
  //CtrlZee message structure
  ctrlzee_peer_cmd_t message = {CTRLZEE_PEER_CMD_REPORT_STATUS, 
                                  ctrlZee_controlLightStatus,
                                  0x00,
                                  0x00};
 
  // Send a status report.
  for(uint8 index=0; index < CTRLZEE_CONTROL_MAX_DEST_ADDR; index++)
  {
    ctrlZee_controlSendMessage(&ctrlZee_controlDestAddress[index], 
                               &message, 
                               4);
  }
}/******************************************************************************/
static void ctrlZee_controlSendMessage(afAddrType_t* destAddress, 
                                       ctrlzee_peer_cmd_t *message, 
                                       byte size)
{
  //Don't send message if the short address has not been initialized.
  if(destAddress->addrMode == afAddrNotPresent)
  {
    return;
  }
  if (AF_DataRequest(destAddress,
                     &ctrlZee_controlEndpointDesc,
                     CTRLZEE_CLUSTER_ID_COMMAND,
                     size,
                     (byte *)message,
                     &ctrlZee_controlMessageId,
                     AF_DISCV_ROUTE,
                     AF_DEFAULT_RADIUS) == afStatus_SUCCESS)
  {
    //TODO: Do something here.
  }
  else
  {
    //TODO: Do something here.
  }
}
/******************************************************************************/
static void ctrlZee_controlSendZDOMatchRequest(void)
{
  // Initiate a Match Description Request (Service Discovery)
  zAddrType_t dstAddr;
  dstAddr.addrMode = AddrBroadcast;
  dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR;
  ZDP_MatchDescReq(&dstAddr,
                   NWK_BROADCAST_SHORTADDR,
                   CTRLZEE_PROFILE_ID,
                   CTRLZEE_CONTROL_OUT_CLUSTER_COUNT, 
                   (cId_t *)ctrlZee_controlOutClusters,
                   CTRLZEE_CONTROL_IN_CLUSTER_COUNT, 
                   (cId_t *)ctrlZee_controlInClusters,
                   FALSE);
}
/******************************************************************************/